CM3D2 Converter.misc_DOPESHEET_MT_editor_menus
1import bpy 2import bmesh 3import math 4import mathutils 5from . import common 6from . import compat 7from .translations.pgettext_functions import * 8 9 10# メニュー等に項目追加 11def menu_func(self, context): 12 row = self.layout.row() 13 row.operator('anim.convert_to_cm3d2_interpolation', icon_value=common.kiss_icon()) 14 15 16def check_fcurve_has_selected_keyframe(fcurve: bpy.types.FCurve) -> bool: 17 for keyframe in fcurve.keyframe_points: 18 if keyframe.select_control_point or keyframe.select_left_handle or keyframe.select_right_handle: 19 return True 20 return False 21 22REPORTS = [] 23 24@compat.BlRegister() 25class CNV_OT_FCURVE_convert_to_cm3d2_interpolation(bpy.types.Operator): 26 bl_idname = 'fcurve.convert_to_cm3d2_interpolation' 27 bl_label = "Convert to CM3D2 Interpolation" 28 bl_description = "Convert keyframes to be compatible with CM3D2 Interpolation" 29 bl_options = {'REGISTER', 'UNDO'} 30 31 only_selected = bpy.props.BoolProperty(name="Only Selected", default=True) 32 keep_reports = bpy.props.BoolProperty(name="Keep Reports", default=False, options={'HIDDEN'}) 33 34 @classmethod 35 def poll(cls, context): 36 fcurve = context.active_editable_fcurve 37 return fcurve 38 39 def invoke(self, context, event): 40 fcurve = context.active_editable_fcurve 41 self.only_selected = check_fcurve_has_selected_keyframe(fcurve) 42 return context.window_manager.invoke_props_dialog(self) 43 44 def draw(self, context): 45 self.layout.prop(self, 'only_selected') 46 47 def do_report(self, **kwargs): 48 if self.keep_reports: 49 REPORTS.append(kwargs) 50 else: 51 self.report(**kwargs) 52 53 @staticmethod 54 def get_slope_vector(from_keyframe: bpy.types.Keyframe, to_keyframe: bpy.types.Keyframe, interpolation=None, backwards=None): 55 if not to_keyframe: 56 interpolation = 'BEZIER' 57 else: 58 if backwards == None: 59 # Figure out which keyframe is in charge of controlling the slope 60 backwards = to_keyframe.co[0] < from_keyframe.co[0] 61 master_keyframe = to_keyframe if backwards else from_keyframe 62 interpolation = interpolation or master_keyframe.interpolation 63 64 if interpolation == 'BEZIER': 65 if backwards: 66 slope_vec = mathutils.Vector(from_keyframe.handle_left ) - mathutils.Vector(from_keyframe.co) 67 else: 68 slope_vec = mathutils.Vector(from_keyframe.handle_right) - mathutils.Vector(from_keyframe.co) 69 elif interpolation == 'LINEAR': 70 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 71 elif interpolation == 'CONSTANT': 72 if backwards: 73 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 74 slope_vec.x /= 3 75 else: 76 slope_vec = mathutils.Vector( (1, 0) ) 77 elif interpolation == 'CONSTANT': 78 if backwards: 79 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 80 slope_vec.x /= 3 81 else: 82 slope_vec = mathutils.Vector( (1, 0) ) 83 elif interpolation == 'CONSTANT': 84 if backwards: 85 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 86 slope_vec.x /= 3 87 else: 88 slope_vec = mathutils.Vector( (1, 0) ) 89 elif interpolation in {'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT'}:#, 'EXPO', 'CIRC'}: # Easing by strength 90 easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing 91 if ( ( easing == 'EASE_IN_OUT' ) 92 or (not backwards and easing == 'EASE_IN' ) 93 or ( backwards and easing == 'EASE_OUT' ) 94 ): 95 slope_vec = mathutils.Vector( (1, 0) ) 96 else: 97 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 98 99 strength = dict() 100 strength['SINE'] = math.sin( (1/3) * math.pi/2 ) 101 strength['QUAD'] = strength['SINE'] ** (1/2) 102 strength['CUBIC'] = strength['QUAD'] ** (1/3) 103 strength['QUART'] = strength['CUBIC'] ** (1/4) 104 strength['QUINT'] = strength['QUART'] ** (1/5) 105 #strength['EXPO'] = strength['QUINT'] ** (1/math.e) 106 #strength['CIRC'] = strength['EXPO'] ** (1/math.pi) 107 108 slope_vec.y *= strength[interpolation] 109 110 slope_vec.x /= 3 111 elif interpolation == 'BACK': # Dynamic easing 112 easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing 113 if ( ( easing == 'EASE_IN_OUT' ) 114 or (not backwards and easing == 'EASE_IN' ) 115 or ( backwards and easing == 'EASE_OUT' ) 116 ): 117 slope_vec = mathutils.Vector( (1, 0) ) 118 else: 119 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 120 # y = a + b(x - x_0) + c(x - x_0)^2 + d(x - x_0)^3 121 # dx = x_3 - x_0 122 # dy = y_3 - y_0 123 # y(x_0) = y_0 = a 124 125 # d = B+1 126 # c = -B 127 128 # y_3 = y_0 + b(dx) - B(dx)^2 + (B+1)dx^3 129 # y_3 - y_0 + B(dx)^2 - (B+1)dx^3 = b(dx) 130 # dy/dx + B(dx) - (B+1)dx^2 = b 131 # dy/dx + B(dx - dx^2) - dx^2 = b 132 133 # y_2 = (1/3)dx(c*dx+2b) + a 134 # y_2 = (1/3)dx((-B)*dx+2b) + y_0 135 136 # y_2 = (1/3)dx(c*dx) + y_0 137 dydx = slope_vec.y / slope_vec.x 138 b = dydx + master_keyframe.back * (slope_vec.x - slope_vec.x**2) - slope_vec.x**2 139 #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x + 2 * b) 140 #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x ) 141 slope_vec.y = master_keyframe.back/3 + slope_vec.y 142 slope_vec.x /= 3 143 144 # d = 2.70158 145 # .33(-1.70158) 146 147 else: 148 slope_vec = mathutils.Vector( (0, 0) ) 149 150 if slope_vec.x != 0: 151 slope_vec *= 1 / slope_vec.x 152 153 return slope_vec 154 155 156 def execute(self, context): 157 factor = 3 158 159 fcurve = context.active_editable_fcurve 160 fcurve.update() 161 162 this_interpolation = None 163 last_interpolation = None 164 165 found_unsupported = set() 166 167 for keyframe_index in range(len(fcurve.keyframe_points)): 168 this_keyframe = fcurve.keyframe_points[keyframe_index ] 169 next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(fcurve.keyframe_points) else None 170 last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0 else None 171 172 last_interpolation = this_interpolation 173 this_interpolation = this_keyframe.interpolation 174 175 apply_in = bool( 176 ( last_keyframe and last_keyframe.interpolation == 'BEZIER' and this_keyframe.select_left_handle ) 177 or this_keyframe.select_control_point 178 or not self.only_selected 179 ) 180 apply_out = bool( 181 ( this_interpolation == 'BEZIER' and this_keyframe.select_right_handle ) 182 or this_keyframe.select_control_point 183 or not self.only_selected 184 ) 185 186 #print(keyframe_index, this_keyframe.select_left_handle, this_keyframe.select_right_handle, this_keyframe.select_control_point, self.only_selected, " | ", apply_in, apply_out) 187 if not apply_in and not apply_out: 188 continue 189 190 this_co = mathutils.Vector(this_keyframe.co) 191 next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None 192 last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None 193 194 vec_in = self.get_slope_vector(this_keyframe, last_keyframe, last_interpolation, backwards=True ) 195 vec_out = self.get_slope_vector(this_keyframe, next_keyframe, this_interpolation, backwards=False) 196 if vec_in.x == 0: 197 apply_in = False 198 found_unsupported.add(last_interpolation) 199 if vec_out.x == 0: 200 apply_out = False 201 found_unsupported.add(this_interpolation) 202 elif apply_out: 203 if this_keyframe.interpolation != 'BEZIER': 204 this_keyframe.interpolation = 'BEZIER' 205 if next_keyframe: 206 next_keyframe.select_left_handle = True 207 208 209 if vec_in.y != vec_out.y: #and not (this_keyframe.handle_left_type == 'ALIGNED' and this_keyframe.handle_right_type == 'ALIGNED'): 210 handle_type = 'FREE' 211 else: 212 handle_type = 'ALIGNED' 213 214 dist_in = (last_co.x - this_co.x) / factor if last_co else this_keyframe.handle_left [0] - this_co.x 215 dist_out = (next_co.x - this_co.x) / factor if next_co else this_keyframe.handle_right[0] - this_co.x 216 217 if apply_in: 218 this_keyframe.handle_left_type = handle_type 219 this_keyframe.handle_left = vec_in * dist_in + this_co 220 if apply_out: 221 this_keyframe.handle_right_type = handle_type 222 this_keyframe.handle_right = vec_out * dist_out + this_co 223 224 225 if found_unsupported: 226 for interpolation_type in found_unsupported: 227 self.do_report(type={'INFO'}, message=f_tip_("'{interpolation}' interpolation not convertable", interpolation=interpolation_type)) 228 self.do_report(type={'WARNING'}, message="Found {count} unsupported interpolation type(s) in {id_data}'s FCurve {fcurve_path}[{fcurve_index}]. See log for more info.".format( 229 count = len(found_unsupported), 230 id_data = fcurve.id_data.name , 231 fcurve_path = fcurve.data_path , 232 fcurve_index = fcurve.array_index ) 233 ) 234 235 return {'FINISHED'} 236 237@compat.BlRegister() 238class CNV_OT_ANIM_convert_to_cm3d2_interpolation(bpy.types.Operator): 239 bl_idname = 'anim.convert_to_cm3d2_interpolation' 240 bl_label = "Convert to CM3D2 Interpolation" 241 bl_description = "Convert keyframes to be compatible with CM3D2 Interpolation" 242 bl_options = {'REGISTER', 'UNDO'} 243 244 only_selected = bpy.props.BoolProperty(name="Only Selected", default=True) 245 items = [ 246 ('FCURVES' , "FCurves" , "", 'FCURVE' , 1), 247 ('KEYFRAMES', "KeyFrames", "", 'KEYFRAME', 2), 248 ] 249 selection_type = bpy.props.EnumProperty(items=items, name="Selection Type", default='FCURVES') 250 251 @classmethod 252 def poll(cls, context): 253 fcurves = context.editable_fcurves 254 if not fcurves: 255 return False 256 return len(fcurves) > 0 257 258 def invoke(self, context, event): 259 fcurves = context.selected_editable_fcurves 260 if not fcurves: 261 fcurves = context.editable_fcurves 262 self.only_selected = False 263 if fcurves: 264 self.selection_type = 'FCURVES' 265 for fcurve in fcurves: 266 if check_fcurve_has_selected_keyframe(fcurve): 267 self.selection_type = 'KEYFRAMES' 268 break 269 return context.window_manager.invoke_props_dialog(self) 270 271 def draw(self, context): 272 row = self.layout.row(align=True) 273 row.alignment = 'LEFT' 274 row.prop(self, 'only_selected') 275 column = row.column(align=True) 276 column.alignment = 'LEFT' 277 column.enabled = self.only_selected 278 column.prop(self, 'selection_type', text='') 279 280 281 def execute(self, context): 282 if self.selection_type == 'FCURVES' and self.only_selected: 283 fcurves = context.selected_editable_fcurves 284 else: 285 fcurves = context.editable_fcurves 286 287 if self.selection_type == 'KEYFRAMES': 288 used_fcurves = [] 289 for fcurve in fcurves: 290 if check_fcurve_has_selected_keyframe(fcurve): 291 print(fcurve) 292 used_fcurves.append(fcurve) 293 fcurves = used_fcurves 294 295 context.window_manager.progress_begin(0, len(fcurves)) 296 for fcurve_index, fcurve in enumerate(fcurves): 297 override = context.copy() 298 override['active_editable_fcurve'] = fcurve 299 bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, 'EXEC_REGION_WIN', only_selected=self.only_selected, keep_reports=True) 300 for kwargs in REPORTS: 301 self.report(**kwargs) 302 REPORTS.clear() 303 #has_reports = bpy.ops.fcurve.convert_to_cm3d2_interpolation.has_reports 304 context.window_manager.progress_update(fcurve_index) 305 return {'FINISHED'}
def
check_fcurve_has_selected_keyframe(fcurve: bpy.types.FCurve) -> bool:
REPORTS =
[]
@compat.BlRegister()
class
CNV_OT_FCURVE_convert_to_cm3d2_interpolation25@compat.BlRegister() 26class CNV_OT_FCURVE_convert_to_cm3d2_interpolation(bpy.types.Operator): 27 bl_idname = 'fcurve.convert_to_cm3d2_interpolation' 28 bl_label = "Convert to CM3D2 Interpolation" 29 bl_description = "Convert keyframes to be compatible with CM3D2 Interpolation" 30 bl_options = {'REGISTER', 'UNDO'} 31 32 only_selected = bpy.props.BoolProperty(name="Only Selected", default=True) 33 keep_reports = bpy.props.BoolProperty(name="Keep Reports", default=False, options={'HIDDEN'}) 34 35 @classmethod 36 def poll(cls, context): 37 fcurve = context.active_editable_fcurve 38 return fcurve 39 40 def invoke(self, context, event): 41 fcurve = context.active_editable_fcurve 42 self.only_selected = check_fcurve_has_selected_keyframe(fcurve) 43 return context.window_manager.invoke_props_dialog(self) 44 45 def draw(self, context): 46 self.layout.prop(self, 'only_selected') 47 48 def do_report(self, **kwargs): 49 if self.keep_reports: 50 REPORTS.append(kwargs) 51 else: 52 self.report(**kwargs) 53 54 @staticmethod 55 def get_slope_vector(from_keyframe: bpy.types.Keyframe, to_keyframe: bpy.types.Keyframe, interpolation=None, backwards=None): 56 if not to_keyframe: 57 interpolation = 'BEZIER' 58 else: 59 if backwards == None: 60 # Figure out which keyframe is in charge of controlling the slope 61 backwards = to_keyframe.co[0] < from_keyframe.co[0] 62 master_keyframe = to_keyframe if backwards else from_keyframe 63 interpolation = interpolation or master_keyframe.interpolation 64 65 if interpolation == 'BEZIER': 66 if backwards: 67 slope_vec = mathutils.Vector(from_keyframe.handle_left ) - mathutils.Vector(from_keyframe.co) 68 else: 69 slope_vec = mathutils.Vector(from_keyframe.handle_right) - mathutils.Vector(from_keyframe.co) 70 elif interpolation == 'LINEAR': 71 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 72 elif interpolation == 'CONSTANT': 73 if backwards: 74 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 75 slope_vec.x /= 3 76 else: 77 slope_vec = mathutils.Vector( (1, 0) ) 78 elif interpolation == 'CONSTANT': 79 if backwards: 80 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 81 slope_vec.x /= 3 82 else: 83 slope_vec = mathutils.Vector( (1, 0) ) 84 elif interpolation == 'CONSTANT': 85 if backwards: 86 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 87 slope_vec.x /= 3 88 else: 89 slope_vec = mathutils.Vector( (1, 0) ) 90 elif interpolation in {'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT'}:#, 'EXPO', 'CIRC'}: # Easing by strength 91 easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing 92 if ( ( easing == 'EASE_IN_OUT' ) 93 or (not backwards and easing == 'EASE_IN' ) 94 or ( backwards and easing == 'EASE_OUT' ) 95 ): 96 slope_vec = mathutils.Vector( (1, 0) ) 97 else: 98 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 99 100 strength = dict() 101 strength['SINE'] = math.sin( (1/3) * math.pi/2 ) 102 strength['QUAD'] = strength['SINE'] ** (1/2) 103 strength['CUBIC'] = strength['QUAD'] ** (1/3) 104 strength['QUART'] = strength['CUBIC'] ** (1/4) 105 strength['QUINT'] = strength['QUART'] ** (1/5) 106 #strength['EXPO'] = strength['QUINT'] ** (1/math.e) 107 #strength['CIRC'] = strength['EXPO'] ** (1/math.pi) 108 109 slope_vec.y *= strength[interpolation] 110 111 slope_vec.x /= 3 112 elif interpolation == 'BACK': # Dynamic easing 113 easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing 114 if ( ( easing == 'EASE_IN_OUT' ) 115 or (not backwards and easing == 'EASE_IN' ) 116 or ( backwards and easing == 'EASE_OUT' ) 117 ): 118 slope_vec = mathutils.Vector( (1, 0) ) 119 else: 120 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 121 # y = a + b(x - x_0) + c(x - x_0)^2 + d(x - x_0)^3 122 # dx = x_3 - x_0 123 # dy = y_3 - y_0 124 # y(x_0) = y_0 = a 125 126 # d = B+1 127 # c = -B 128 129 # y_3 = y_0 + b(dx) - B(dx)^2 + (B+1)dx^3 130 # y_3 - y_0 + B(dx)^2 - (B+1)dx^3 = b(dx) 131 # dy/dx + B(dx) - (B+1)dx^2 = b 132 # dy/dx + B(dx - dx^2) - dx^2 = b 133 134 # y_2 = (1/3)dx(c*dx+2b) + a 135 # y_2 = (1/3)dx((-B)*dx+2b) + y_0 136 137 # y_2 = (1/3)dx(c*dx) + y_0 138 dydx = slope_vec.y / slope_vec.x 139 b = dydx + master_keyframe.back * (slope_vec.x - slope_vec.x**2) - slope_vec.x**2 140 #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x + 2 * b) 141 #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x ) 142 slope_vec.y = master_keyframe.back/3 + slope_vec.y 143 slope_vec.x /= 3 144 145 # d = 2.70158 146 # .33(-1.70158) 147 148 else: 149 slope_vec = mathutils.Vector( (0, 0) ) 150 151 if slope_vec.x != 0: 152 slope_vec *= 1 / slope_vec.x 153 154 return slope_vec 155 156 157 def execute(self, context): 158 factor = 3 159 160 fcurve = context.active_editable_fcurve 161 fcurve.update() 162 163 this_interpolation = None 164 last_interpolation = None 165 166 found_unsupported = set() 167 168 for keyframe_index in range(len(fcurve.keyframe_points)): 169 this_keyframe = fcurve.keyframe_points[keyframe_index ] 170 next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(fcurve.keyframe_points) else None 171 last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0 else None 172 173 last_interpolation = this_interpolation 174 this_interpolation = this_keyframe.interpolation 175 176 apply_in = bool( 177 ( last_keyframe and last_keyframe.interpolation == 'BEZIER' and this_keyframe.select_left_handle ) 178 or this_keyframe.select_control_point 179 or not self.only_selected 180 ) 181 apply_out = bool( 182 ( this_interpolation == 'BEZIER' and this_keyframe.select_right_handle ) 183 or this_keyframe.select_control_point 184 or not self.only_selected 185 ) 186 187 #print(keyframe_index, this_keyframe.select_left_handle, this_keyframe.select_right_handle, this_keyframe.select_control_point, self.only_selected, " | ", apply_in, apply_out) 188 if not apply_in and not apply_out: 189 continue 190 191 this_co = mathutils.Vector(this_keyframe.co) 192 next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None 193 last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None 194 195 vec_in = self.get_slope_vector(this_keyframe, last_keyframe, last_interpolation, backwards=True ) 196 vec_out = self.get_slope_vector(this_keyframe, next_keyframe, this_interpolation, backwards=False) 197 if vec_in.x == 0: 198 apply_in = False 199 found_unsupported.add(last_interpolation) 200 if vec_out.x == 0: 201 apply_out = False 202 found_unsupported.add(this_interpolation) 203 elif apply_out: 204 if this_keyframe.interpolation != 'BEZIER': 205 this_keyframe.interpolation = 'BEZIER' 206 if next_keyframe: 207 next_keyframe.select_left_handle = True 208 209 210 if vec_in.y != vec_out.y: #and not (this_keyframe.handle_left_type == 'ALIGNED' and this_keyframe.handle_right_type == 'ALIGNED'): 211 handle_type = 'FREE' 212 else: 213 handle_type = 'ALIGNED' 214 215 dist_in = (last_co.x - this_co.x) / factor if last_co else this_keyframe.handle_left [0] - this_co.x 216 dist_out = (next_co.x - this_co.x) / factor if next_co else this_keyframe.handle_right[0] - this_co.x 217 218 if apply_in: 219 this_keyframe.handle_left_type = handle_type 220 this_keyframe.handle_left = vec_in * dist_in + this_co 221 if apply_out: 222 this_keyframe.handle_right_type = handle_type 223 this_keyframe.handle_right = vec_out * dist_out + this_co 224 225 226 if found_unsupported: 227 for interpolation_type in found_unsupported: 228 self.do_report(type={'INFO'}, message=f_tip_("'{interpolation}' interpolation not convertable", interpolation=interpolation_type)) 229 self.do_report(type={'WARNING'}, message="Found {count} unsupported interpolation type(s) in {id_data}'s FCurve {fcurve_path}[{fcurve_index}]. See log for more info.".format( 230 count = len(found_unsupported), 231 id_data = fcurve.id_data.name , 232 fcurve_path = fcurve.data_path , 233 fcurve_index = fcurve.array_index ) 234 ) 235 236 return {'FINISHED'}
only_selected: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Only Selected', 'default': True, 'attr': 'only_selected'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Only Selected', 'default': True, 'attr': 'only_selected'}>
keep_reports: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Keep Reports', 'default': False, 'options': {'HIDDEN'}, 'attr': 'keep_reports'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Keep Reports', 'default': False, 'options': {'HIDDEN'}, 'attr': 'keep_reports'}>
@staticmethod
def
get_slope_vector( from_keyframe: bpy.types.Keyframe, to_keyframe: bpy.types.Keyframe, interpolation=None, backwards=None):
54 @staticmethod 55 def get_slope_vector(from_keyframe: bpy.types.Keyframe, to_keyframe: bpy.types.Keyframe, interpolation=None, backwards=None): 56 if not to_keyframe: 57 interpolation = 'BEZIER' 58 else: 59 if backwards == None: 60 # Figure out which keyframe is in charge of controlling the slope 61 backwards = to_keyframe.co[0] < from_keyframe.co[0] 62 master_keyframe = to_keyframe if backwards else from_keyframe 63 interpolation = interpolation or master_keyframe.interpolation 64 65 if interpolation == 'BEZIER': 66 if backwards: 67 slope_vec = mathutils.Vector(from_keyframe.handle_left ) - mathutils.Vector(from_keyframe.co) 68 else: 69 slope_vec = mathutils.Vector(from_keyframe.handle_right) - mathutils.Vector(from_keyframe.co) 70 elif interpolation == 'LINEAR': 71 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 72 elif interpolation == 'CONSTANT': 73 if backwards: 74 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 75 slope_vec.x /= 3 76 else: 77 slope_vec = mathutils.Vector( (1, 0) ) 78 elif interpolation == 'CONSTANT': 79 if backwards: 80 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 81 slope_vec.x /= 3 82 else: 83 slope_vec = mathutils.Vector( (1, 0) ) 84 elif interpolation == 'CONSTANT': 85 if backwards: 86 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 87 slope_vec.x /= 3 88 else: 89 slope_vec = mathutils.Vector( (1, 0) ) 90 elif interpolation in {'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT'}:#, 'EXPO', 'CIRC'}: # Easing by strength 91 easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing 92 if ( ( easing == 'EASE_IN_OUT' ) 93 or (not backwards and easing == 'EASE_IN' ) 94 or ( backwards and easing == 'EASE_OUT' ) 95 ): 96 slope_vec = mathutils.Vector( (1, 0) ) 97 else: 98 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 99 100 strength = dict() 101 strength['SINE'] = math.sin( (1/3) * math.pi/2 ) 102 strength['QUAD'] = strength['SINE'] ** (1/2) 103 strength['CUBIC'] = strength['QUAD'] ** (1/3) 104 strength['QUART'] = strength['CUBIC'] ** (1/4) 105 strength['QUINT'] = strength['QUART'] ** (1/5) 106 #strength['EXPO'] = strength['QUINT'] ** (1/math.e) 107 #strength['CIRC'] = strength['EXPO'] ** (1/math.pi) 108 109 slope_vec.y *= strength[interpolation] 110 111 slope_vec.x /= 3 112 elif interpolation == 'BACK': # Dynamic easing 113 easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing 114 if ( ( easing == 'EASE_IN_OUT' ) 115 or (not backwards and easing == 'EASE_IN' ) 116 or ( backwards and easing == 'EASE_OUT' ) 117 ): 118 slope_vec = mathutils.Vector( (1, 0) ) 119 else: 120 slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co) 121 # y = a + b(x - x_0) + c(x - x_0)^2 + d(x - x_0)^3 122 # dx = x_3 - x_0 123 # dy = y_3 - y_0 124 # y(x_0) = y_0 = a 125 126 # d = B+1 127 # c = -B 128 129 # y_3 = y_0 + b(dx) - B(dx)^2 + (B+1)dx^3 130 # y_3 - y_0 + B(dx)^2 - (B+1)dx^3 = b(dx) 131 # dy/dx + B(dx) - (B+1)dx^2 = b 132 # dy/dx + B(dx - dx^2) - dx^2 = b 133 134 # y_2 = (1/3)dx(c*dx+2b) + a 135 # y_2 = (1/3)dx((-B)*dx+2b) + y_0 136 137 # y_2 = (1/3)dx(c*dx) + y_0 138 dydx = slope_vec.y / slope_vec.x 139 b = dydx + master_keyframe.back * (slope_vec.x - slope_vec.x**2) - slope_vec.x**2 140 #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x + 2 * b) 141 #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x ) 142 slope_vec.y = master_keyframe.back/3 + slope_vec.y 143 slope_vec.x /= 3 144 145 # d = 2.70158 146 # .33(-1.70158) 147 148 else: 149 slope_vec = mathutils.Vector( (0, 0) ) 150 151 if slope_vec.x != 0: 152 slope_vec *= 1 / slope_vec.x 153 154 return slope_vec
def
execute(self, context):
157 def execute(self, context): 158 factor = 3 159 160 fcurve = context.active_editable_fcurve 161 fcurve.update() 162 163 this_interpolation = None 164 last_interpolation = None 165 166 found_unsupported = set() 167 168 for keyframe_index in range(len(fcurve.keyframe_points)): 169 this_keyframe = fcurve.keyframe_points[keyframe_index ] 170 next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(fcurve.keyframe_points) else None 171 last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0 else None 172 173 last_interpolation = this_interpolation 174 this_interpolation = this_keyframe.interpolation 175 176 apply_in = bool( 177 ( last_keyframe and last_keyframe.interpolation == 'BEZIER' and this_keyframe.select_left_handle ) 178 or this_keyframe.select_control_point 179 or not self.only_selected 180 ) 181 apply_out = bool( 182 ( this_interpolation == 'BEZIER' and this_keyframe.select_right_handle ) 183 or this_keyframe.select_control_point 184 or not self.only_selected 185 ) 186 187 #print(keyframe_index, this_keyframe.select_left_handle, this_keyframe.select_right_handle, this_keyframe.select_control_point, self.only_selected, " | ", apply_in, apply_out) 188 if not apply_in and not apply_out: 189 continue 190 191 this_co = mathutils.Vector(this_keyframe.co) 192 next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None 193 last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None 194 195 vec_in = self.get_slope_vector(this_keyframe, last_keyframe, last_interpolation, backwards=True ) 196 vec_out = self.get_slope_vector(this_keyframe, next_keyframe, this_interpolation, backwards=False) 197 if vec_in.x == 0: 198 apply_in = False 199 found_unsupported.add(last_interpolation) 200 if vec_out.x == 0: 201 apply_out = False 202 found_unsupported.add(this_interpolation) 203 elif apply_out: 204 if this_keyframe.interpolation != 'BEZIER': 205 this_keyframe.interpolation = 'BEZIER' 206 if next_keyframe: 207 next_keyframe.select_left_handle = True 208 209 210 if vec_in.y != vec_out.y: #and not (this_keyframe.handle_left_type == 'ALIGNED' and this_keyframe.handle_right_type == 'ALIGNED'): 211 handle_type = 'FREE' 212 else: 213 handle_type = 'ALIGNED' 214 215 dist_in = (last_co.x - this_co.x) / factor if last_co else this_keyframe.handle_left [0] - this_co.x 216 dist_out = (next_co.x - this_co.x) / factor if next_co else this_keyframe.handle_right[0] - this_co.x 217 218 if apply_in: 219 this_keyframe.handle_left_type = handle_type 220 this_keyframe.handle_left = vec_in * dist_in + this_co 221 if apply_out: 222 this_keyframe.handle_right_type = handle_type 223 this_keyframe.handle_right = vec_out * dist_out + this_co 224 225 226 if found_unsupported: 227 for interpolation_type in found_unsupported: 228 self.do_report(type={'INFO'}, message=f_tip_("'{interpolation}' interpolation not convertable", interpolation=interpolation_type)) 229 self.do_report(type={'WARNING'}, message="Found {count} unsupported interpolation type(s) in {id_data}'s FCurve {fcurve_path}[{fcurve_index}]. See log for more info.".format( 230 count = len(found_unsupported), 231 id_data = fcurve.id_data.name , 232 fcurve_path = fcurve.data_path , 233 fcurve_index = fcurve.array_index ) 234 ) 235 236 return {'FINISHED'}
Inherited Members
- bpy_types.Operator
- as_keywords
- poll_message_set
- builtins.bpy_struct
- keys
- values
- items
- get
- pop
- as_pointer
- keyframe_insert
- keyframe_delete
- driver_add
- driver_remove
- is_property_set
- property_unset
- is_property_readonly
- is_property_overridable_library
- property_overridable_library_set
- path_resolve
- path_from_id
- type_recast
- bl_rna_get_subclass_py
- bl_rna_get_subclass
- id_properties_ensure
- id_properties_clear
- id_properties_ui
- id_data
@compat.BlRegister()
class
CNV_OT_ANIM_convert_to_cm3d2_interpolation238@compat.BlRegister() 239class CNV_OT_ANIM_convert_to_cm3d2_interpolation(bpy.types.Operator): 240 bl_idname = 'anim.convert_to_cm3d2_interpolation' 241 bl_label = "Convert to CM3D2 Interpolation" 242 bl_description = "Convert keyframes to be compatible with CM3D2 Interpolation" 243 bl_options = {'REGISTER', 'UNDO'} 244 245 only_selected = bpy.props.BoolProperty(name="Only Selected", default=True) 246 items = [ 247 ('FCURVES' , "FCurves" , "", 'FCURVE' , 1), 248 ('KEYFRAMES', "KeyFrames", "", 'KEYFRAME', 2), 249 ] 250 selection_type = bpy.props.EnumProperty(items=items, name="Selection Type", default='FCURVES') 251 252 @classmethod 253 def poll(cls, context): 254 fcurves = context.editable_fcurves 255 if not fcurves: 256 return False 257 return len(fcurves) > 0 258 259 def invoke(self, context, event): 260 fcurves = context.selected_editable_fcurves 261 if not fcurves: 262 fcurves = context.editable_fcurves 263 self.only_selected = False 264 if fcurves: 265 self.selection_type = 'FCURVES' 266 for fcurve in fcurves: 267 if check_fcurve_has_selected_keyframe(fcurve): 268 self.selection_type = 'KEYFRAMES' 269 break 270 return context.window_manager.invoke_props_dialog(self) 271 272 def draw(self, context): 273 row = self.layout.row(align=True) 274 row.alignment = 'LEFT' 275 row.prop(self, 'only_selected') 276 column = row.column(align=True) 277 column.alignment = 'LEFT' 278 column.enabled = self.only_selected 279 column.prop(self, 'selection_type', text='') 280 281 282 def execute(self, context): 283 if self.selection_type == 'FCURVES' and self.only_selected: 284 fcurves = context.selected_editable_fcurves 285 else: 286 fcurves = context.editable_fcurves 287 288 if self.selection_type == 'KEYFRAMES': 289 used_fcurves = [] 290 for fcurve in fcurves: 291 if check_fcurve_has_selected_keyframe(fcurve): 292 print(fcurve) 293 used_fcurves.append(fcurve) 294 fcurves = used_fcurves 295 296 context.window_manager.progress_begin(0, len(fcurves)) 297 for fcurve_index, fcurve in enumerate(fcurves): 298 override = context.copy() 299 override['active_editable_fcurve'] = fcurve 300 bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, 'EXEC_REGION_WIN', only_selected=self.only_selected, keep_reports=True) 301 for kwargs in REPORTS: 302 self.report(**kwargs) 303 REPORTS.clear() 304 #has_reports = bpy.ops.fcurve.convert_to_cm3d2_interpolation.has_reports 305 context.window_manager.progress_update(fcurve_index) 306 return {'FINISHED'}
only_selected: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Only Selected', 'default': True, 'attr': 'only_selected'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Only Selected', 'default': True, 'attr': 'only_selected'}>
selection_type: <_PropertyDeferred, <built-in function EnumProperty>, {'items': [('FCURVES', 'FCurves', '', 'FCURVE', 1), ('KEYFRAMES', 'KeyFrames', '', 'KEYFRAME', 2)], 'name': 'Selection Type', 'default': 'FCURVES', 'attr': 'selection_type'}> =
<_PropertyDeferred, <built-in function EnumProperty>, {'items': [('FCURVES', 'FCurves', '', 'FCURVE', 1), ('KEYFRAMES', 'KeyFrames', '', 'KEYFRAME', 2)], 'name': 'Selection Type', 'default': 'FCURVES', 'attr': 'selection_type'}>
def
invoke(self, context, event):
259 def invoke(self, context, event): 260 fcurves = context.selected_editable_fcurves 261 if not fcurves: 262 fcurves = context.editable_fcurves 263 self.only_selected = False 264 if fcurves: 265 self.selection_type = 'FCURVES' 266 for fcurve in fcurves: 267 if check_fcurve_has_selected_keyframe(fcurve): 268 self.selection_type = 'KEYFRAMES' 269 break 270 return context.window_manager.invoke_props_dialog(self)
def
execute(self, context):
282 def execute(self, context): 283 if self.selection_type == 'FCURVES' and self.only_selected: 284 fcurves = context.selected_editable_fcurves 285 else: 286 fcurves = context.editable_fcurves 287 288 if self.selection_type == 'KEYFRAMES': 289 used_fcurves = [] 290 for fcurve in fcurves: 291 if check_fcurve_has_selected_keyframe(fcurve): 292 print(fcurve) 293 used_fcurves.append(fcurve) 294 fcurves = used_fcurves 295 296 context.window_manager.progress_begin(0, len(fcurves)) 297 for fcurve_index, fcurve in enumerate(fcurves): 298 override = context.copy() 299 override['active_editable_fcurve'] = fcurve 300 bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, 'EXEC_REGION_WIN', only_selected=self.only_selected, keep_reports=True) 301 for kwargs in REPORTS: 302 self.report(**kwargs) 303 REPORTS.clear() 304 #has_reports = bpy.ops.fcurve.convert_to_cm3d2_interpolation.has_reports 305 context.window_manager.progress_update(fcurve_index) 306 return {'FINISHED'}
Inherited Members
- bpy_types.Operator
- as_keywords
- poll_message_set
- builtins.bpy_struct
- keys
- values
- get
- pop
- as_pointer
- keyframe_insert
- keyframe_delete
- driver_add
- driver_remove
- is_property_set
- property_unset
- is_property_readonly
- is_property_overridable_library
- property_overridable_library_set
- path_resolve
- path_from_id
- type_recast
- bl_rna_get_subclass_py
- bl_rna_get_subclass
- id_properties_ensure
- id_properties_clear
- id_properties_ui
- id_data